package http_client

import (
	"context"
	"errors"
	"fmt"
	"git.mycaigou.com/gfyx/common/cache"
	httpTools "git.mycaigou.com/gfyx/common/http_client"
	"git.myscrm.cn/golang/stark/v4"
)

const (
	BaiYingHost          = "byai.host"
	BaiYingClientId      = "byai.client_id"
	BaiYingClientSecret  = "byai.client_secret"
	CacheTokenKeyPrefix  = "gfyx:byai:token:"
	ErrorCodeTokenExpire = 40000010
)

// 百应的错误码
//40000001	未指定clientId	请求时传入 clientId
//40000002	未找到 clientId 对应的 APP	请确定 AppId 的正确性
//40000009	未指定 AccessToken	请求时传入 AcessToken
//40000010	AccessToken 不存在或者过期	请传入有效 AccessToken
//40000011	无效的 AccessToken	请传入有效 AccessToken
//10000401	校验数据错误	统内部核对入参是否必传，值是否在指定范围
//10000412	参数错误	核对参数传的值是否存在，是否有权限操作
//10000404	数据不存在	核对入参是否正确

type BaiYingHttpClient struct {
	host         string
	clientId     string
	clientSecret string
	companyId    int64
	client       *httpTools.HttpClientTool
}

type tokenResponse struct {
	Success   bool        `json:"success"`
	Code      int         `json:"code"`
	Message   string      `json:"message"`
	RequestId interface{} `json:"requestId"`
	Data      struct {
		AccessToken string `json:"access_token"`
		Scope       string `json:"scope"`
		ExpiresIn   int    `json:"expires_in"`
	} `json:"data"`
}

type commonResponse struct {
	Code int `json:"code"`
}

type tokenRequest struct {
	CompanyId    int64  `json:"company_id"`
	ClientId     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

func NewBaiYingHttpClient(companyId int64) *BaiYingHttpClient {
	return &BaiYingHttpClient{
		host:         stark.Configer.(stark.ApolloConfigerIface).GetStringValue(BaiYingHost, ""),
		clientId:     stark.Configer.(stark.ApolloConfigerIface).GetStringValue(BaiYingClientId, ""),
		clientSecret: stark.Configer.(stark.ApolloConfigerIface).GetStringValue(BaiYingClientSecret, ""),
		companyId:    companyId,
		client:       httpTools.New(),
	}
}

// GetToken 获取接口缓存
func (c *BaiYingHttpClient) getToken(ctx context.Context, allowCache bool) (string, error) {
	// 优先从 redis 获取
	cacheKey := fmt.Sprintf("%s%d", CacheTokenKeyPrefix, c.companyId)
	cRedis, err := cache.Redis()
	if err != nil {
		stark.Logger.Errorf(ctx, "获取 redis 链接失败，原因：%+v", err)
		return "", err
	}
	if allowCache {
		cacheToken, err := cRedis.Get(ctx, cacheKey)
		if err != nil {
			stark.Logger.Errorf(ctx, "获取缓存 key 失效，key=%s, err=%+v", cacheKey, err)
			return "", err
		}
		if cacheToken != "" {
			return cacheToken, nil
		}
	}
	// 调用接口获取
	request := tokenRequest{
		CompanyId:    c.companyId,
		ClientId:     c.clientId,
		ClientSecret: c.clientSecret,
	}
	response := &tokenResponse{}
	url := c.host + "/oauth/token"
	headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
	_, _, errs := c.client.Post(ctx, url, request, headers).EndStruct(&response)
	if errs != nil && len(errs) > 0 {
		stark.Logger.Errorf(ctx, "请求 baiying api get token 失败，err:%+v", errs)
		return "", errs[0]
	}
	if !response.Success {
		stark.Logger.Errorf(ctx, "baiying get token 失败，err:%+v", response)
		return "", errors.New(response.Message)
	}
	_, err = cRedis.Set(ctx, cacheKey, response.Data.AccessToken, true, response.Data.ExpiresIn)
	if err != nil {
		stark.Logger.Errorf(ctx, "设置 baiying token 缓存 值失败:", err)
		return "", err
	}
	return response.Data.AccessToken, nil
}

func (c *BaiYingHttpClient) Post(ctx context.Context, path string, params map[string]interface{}, header map[string]string) *httpTools.HttpClientTool {
	url := c.host + path
	token, err := c.getToken(ctx, true)
	if err != nil {
		stark.Logger.Errorf(ctx, "baiying 获取token失败，err:%+v", err)
	}
	requestParams := map[string]interface{}{
		"companyId":   c.companyId,
		"accessToken": token,
	}
	urlWithParams := httpTools.JoinQueryStr(url, requestParams)
	if header == nil {
		header = map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
	} else {
		header["Content-Type"] = "application/x-www-form-urlencoded"
	}

	c.client = c.client.Post(ctx, urlWithParams, params, header)
	// token 失效重试
	response := &commonResponse{}
	c.client.EndStruct(&response)
	if response.Code == ErrorCodeTokenExpire {
		// 重新获取 token
		token, err = c.getToken(ctx, false)
		if err != nil {
			stark.Logger.Errorf(ctx, "baiying 重新获取token失败，err:%+v", err)
		}
		// 重试请求
		requestParams["accessToken"] = token
		c.client = c.client.Post(ctx, urlWithParams, params, header)
	}

	return c.client
}

func (c *BaiYingHttpClient) Get(ctx context.Context, path string, params map[string]interface{}) *httpTools.HttpClientTool {
	url := c.host + path
	token, err := c.getToken(ctx, true)
	if err != nil {
		stark.Logger.Errorf(ctx, "baiying 获取token失败，err:%+v", err)
	}
	params["companyId"] = c.companyId
	params["accessToken"] = token
	urlWithParams := httpTools.JoinQueryStr(url, params)

	c.client = c.client.Get(ctx, urlWithParams, nil)

	// token 失效重试
	response := &commonResponse{}
	c.client.EndStruct(&response)
	if response.Code == ErrorCodeTokenExpire {
		// 重新获取 token
		token, err = c.getToken(ctx, false)
		if err != nil {
			stark.Logger.Errorf(ctx, "baiying 重新获取token失败，err:%+v", err)
		}
		// 重试请求
		params["accessToken"] = token
		c.client = c.client.Get(ctx, urlWithParams, nil)
	}

	return c.client
}
